const uniapiNames = ["base64ToArrayBuffer", "arrayBufferToBase64", "addInterceptor", "removeInterceptor",
	"interceptors", "request", "uploadFile", "downloadFile", "connectSocket", "onSocketOpen", "onSocketError",
	"sendSocketMessage", "onSocketMessage", "closeSocket", "onSocketClose", "getUpdateManager", "configMTLS",
	"navigateTo", "redirectTo", "reLaunch", "switchTab", "navigateBack", "setStorage", "setStorageSync",
	"getStorage", "getStorageSync", "getStorageInfo", "getStorageInfoSync", "removeStorage", "removeStorageSync",
	"clearStorage", "clearStorageSync", "getLocation", "chooseLocation", "openLocation", "createMapContext",
	"chooseImage", "chooseFile", "previewImage", "closePreviewImage", "getImageInfo", "getVideoInfo",
	"saveImageToPhotosAlbum", "compressImage", "compressVideo", "getRecorderManager", "getBackgroundAudioManager",
	"createAudioContext", "createInnerAudioContext", "chooseVideo", "saveVideoToPhotosAlbum", "createVideoContext",
	"createCameraContext", "createLivePlayerContext", "createLivePusherContext", "getSystemInfo",
	"getSystemInfoSync", "getWindowInfo", "getDeviceInfo", "getAppBaseInfo", "canIUse", "onMemoryWarning",
	"getNetworkType", "onNetworkStatusChange", "offNetworkStatusChange", "onAccelerometerChange",
	"offAccelerometerChange", "startAccelerometer", "stopAccelerometer", "onCompassChange", "offCompassChange",
	"startCompass", "stopCompass", "onGyroscopeChange", "startGyroscope", "stopGyroscope", "makePhoneCall",
	"scanCode", "setClipboardData", "getClipboardData", "setScreenBrightness", "getScreenBrightness",
	"setKeepScreenOn", "onUserCaptureScreen", "vibrateLong", "vibrateShort", "addPhoneContact",
	"openBluetoothAdapter", "startBluetoothDevicesDiscovery", "onBluetoothDeviceFound",
	"stopBluetoothDevicesDiscovery", "onBluetoothAdapterStateChange", "getConnectedBluetoothDevices",
	"getBluetoothDevices", "getBluetoothAdapterState", "closeBluetoothAdapter", "writeBLECharacteristicValue",
	"readBLECharacteristicValue", "onBLEConnectionStateChange", "onBLECharacteristicValueChange",
	"notifyBLECharacteristicValueChange", "getBLEDeviceServices", "getBLEDeviceCharacteristics",
	"createBLEConnection", "closeBLEConnection", "setBLEMTU", "getBLEDeviceRSSI", "onBeaconServiceChange",
	"onBeaconUpdate", "getBeacons", "startBeaconDiscovery", "stopBeaconDiscovery",
	"checkIsSupportSoterAuthentication", "checkIsSoterEnrolledInDevice", "startSoterAuthentication",
	"onThemeChange", "onUIStyleChange", "getSystemSetting", "getAppAuthorizeSetting", "openAppAuthorizeSetting",
	"hideKeyboard", "onKeyboardHeightChange", "offKeyboardHeightChange", "getSelectedTextRange", "showToast",
	"hideToast", "showLoading", "hideLoading", "showModal", "showActionSheet", "setNavigationBarTitle",
	"setNavigationBarColor", "showNavigationBarLoading", "hideNavigationBarLoading", "setTabBarItem",
	"setTabBarStyle", "hideTabBar", "showTabBar", "setTabBarBadge", "removeTabBarBadge", "showTabBarRedDot",
	"hideTabBarRedDot", "onTabBarMidButtonTap", "setBackgroundColor", "setBackgroundTextStyle", "createAnimation",
	"pageScrollTo", "onWindowResize", "offWindowResize", "loadFontFace", "startPullDownRefresh",
	"stopPullDownRefresh", "createSelectorQuery", "createIntersectionObserver", "createMediaQueryObserver",
	"getMenuButtonBoundingClientRect", "showTopWindow", "showLeftWindow", "showRightWindow", "hideTopWindow",
	"hideLeftWindow", "hideRightWindow", "getTopWindowStyle", "getLeftWindowStyle", "getRightWindowStyle",
	"setTopWindowStyle", "setLeftWindowStyle", "setRightWindowStyle", "getLocale", "setLocale", "onLocaleChange",
	"$emit", "$on", "$once", "$off", "saveFile", "getSavedFileList", "getSavedFileInfo", "removeSavedFile",
	"getFileInfo", "openDocument", "getFileSystemManager", "createOffscreenCanvas", "createCanvasContext",
	"canvasToTempFilePath", "canvasPutImageData", "canvasGetImageData", "getProvider", "login", "checkSession",
	"getUserInfo", "getUserProfile", "preLogin", "closeAuthView", "getCheckBoxState", "getUniverifyManager",
	"share", "shareWithSystem", "showShareMenu", "hideShareMenu", "requestPayment", "subscribePush",
	"unsubscribePush", "onPush", "offPush", "requireNativePlugin", "upx2px", "restoreGlobal", "requireGlobal",
	"getSubNVueById", "getCurrentSubNVue", "setPageMeta", "onHostEventReceive", "onNativeEventReceive",
	"sendNativeEvent", "preloadPage", "unPreloadPage", "loadSubPackage", "sendHostEvent", "navigateToMiniProgram",
	"getLaunchOptionsSync", "getEnterOptionsSync", "createRewardedVideoAd", "createFullScreenVideoAd",
	"createInterstitialAd", "createInteractiveAd", "invokePushCallback", "getPushClientId", "onPushMessage",
	"offPushMessage", "createPushMessage", "__$wx__", "_globalUniCloudObj", "$u"
]

const callbackPool = new Map();
/**
 * uniapi兼容类
 * 负责生成与uniapp api相同的方法调用；
 * 在方法被调用时会将调用参数转发给网页外部的app层，app层调用结束后再将结果反馈给网页
 * 目前已实现大部分api的兼容
 * @author xueyanjun
 * @since 2023-01-06
 */
class UniBridge {

	constructor() {
		this._init();
	}

	_init() {
		uniapiNames.forEach(name => {
			if (this[name] == null) {
				this[name] = this._defaultInvokeCall(name);
			}
		});
	}

	_defaultInvokeCall(apiName) {
		return (arg) => {
			const randomId = this.$xwbridge.genNonDuplicateId();
			const successCallback = arg.success;
			const failCallback = arg.fail;
			const completeCallback = arg.complete || (() => {});
			if (successCallback) {
				arg.success = successCallback.__cid__ = 's-' + randomId;
				callbackPool.set(arg.success, (res) => {
					successCallback(res);
				});
			}
			if (failCallback) {
				arg.fail = failCallback.__cid__ = 'f-' + randomId;
				callbackPool.set(arg.fail, (res) => {
					failCallback(res);
				});
			}
			if (successCallback || failCallback) {
				// complete函数总是传递用来删除无用的回调函数
				arg.complete = completeCallback.__cid__ = 'c-' + randomId;
				callbackPool.set(arg.complete, (res) => {
					clearCallback();
					completeCallback(res);
				});
			}
			const clearCallback = () => {
				Promise.resolve().then(() => {
					if (successCallback) {
						callbackPool.delete(successCallback.__cid__);
					}
					if (failCallback) {
						callbackPool.delete(failCallback.__cid__);
					}
					callbackPool.delete(completeCallback.__cid__);
				});
			}
			const param = {
				a: 'uniapi',
				d: {
					me: apiName,
					op: arg
				},
			}
			this.$xwbridge._postMsg(param);
		}
	}

	_defaultRouteSwitch(routeName, arg) {
		const xwAction = arg.xwAction || 'windowHide';
		const xwAnimate = arg.animationType || arg.xwAnimate;
		const xwDuration = arg.animationDuration || arg.xwDuration;
		const successCallback = arg.success;
		arg.success = () => {
			const actionfn = this.$xwbridge[xwAction];
			if (actionfn) {
				actionfn.call(this.$xwbridge, {
					ani: xwAnimate,
					duration: xwDuration
				});
			}
			if (successCallback) {
				successCallback();
			}
		}
		if (arg.xwAction) {
			delete arg.xwAction;
		}
		if (arg.xwAnimate) {
			delete arg.xwAnimate;
		}
		this._defaultInvokeCall(routeName)(arg);
	}

	setStorage(arg) {
		if (typeof arg.data === 'number') {
			arg.data = arg.data.toString();
		} else if (typeof arg.data === 'object') {
			arg.data = JSON.stringify(data);
		}
		this.$xwbridge.$plus.storage.setItemAsync(arg.key, arg.data, arg.success, arg.fail);
	}

	setStorageSync(key, data) {
		let val;
		if (typeof data === 'number') {
			val = data.toString();
		} else if (typeof data === 'object') {
			val = JSON.stringify(data);
		} else {
			val = data;
		}
		this.$xwbridge.$plus.storage.setItem(key, val);
	}

	getStorage(arg) {
		this.$xwbridge.$plus.storage.getItemAsync(arg.key, arg.success, arg.fail);
	}

	getStorageSync(key) {
		return this.$xwbridge.$plus.storage.getItem(key);
	}

	removeStorage(arg) {
		this.$xwbridge.$plus.storage.removeItemAsync(arg.key, arg.success, arg.fail)
	}

	removeStorageSync(key) {
		this.$xwbridge.$plus.storage.removeItem(key);
	}

	clearStorage(arg) {
		this.$xwbridge.$plus.storage.clearAsync(arg.success, arg.fail);
	}

	clearStorageSync() {
		this.$xwbridge.$plus.storage.clear();
	}

	getSystemInfoSync() {
		if (this._systemInfo == null) {
			const sinfo = this.getStorageSync('env:systeminfo');
			if (sinfo) {
				this._systemInfo = JSON.parse(sinfo);
			}
		}
		return this._systemInfo;
	}

	getSystemInfo(arg) {
		if (this._systemInfo) {
			arg.success(this._systemInfo);
		} else {
			this._defaultInvokeCall('getSystemInfo')(arg);
		}
	}

	getWindowInfo() {
		const sysInfo = this.getSystemInfoSync();
		const props = ['pixelRatio', 'screenWidth', 'screenHeight', 'windowWidth', 'windowHeight', 'windowTop',
			'windowBottom', 'statusBarHeight', 'screenTop', 'safeArea', 'safeAreaInsets'
		];
		const windowInfo = {};
		for(let p of props){
			windowInfo[p] = sysInfo[p];
		}
		return windowInfo;
	}
	/*获取设备设置,注意与uniapp原api不同,这里需要异步接收返回值*/
	getSystemSetting({success}){
		this._defaultInvokeCall('getSystemSetting')({success});
	}

	switchTab(arg) {
		arg = arg || {};
		arg.xwDuration = 100;
		this._defaultRouteSwitch('switchTab', arg);
	}

	navigateBack(arg) {
		arg = arg || {};
		if (arg.delta == null || arg.delta === 1) {
			const xwAction = arg.xwAction || 'windowHide';
			const xwAnimate = arg.animationType || arg.xwAnimate;
			const xwDuration = arg.animationDuration || arg.xwDuration;
			const actionfn = this.$xwbridge[xwAction];
			if (actionfn) {
				actionfn.call(this.$xwbridge, {
					ani: xwAnimate,
					duration: xwDuration
				});
			}
			if (arg.success) {
				arg.success();
			}
		} else {
			arg.delta = arg.delta - 1;
			this._defaultRouteSwitch('navigateBack', arg);
		}
	}

	redirectTo(arg) {
		arg = arg || {};
		arg.xwAnimate = 'none';
		this._defaultRouteSwitch('navigateTo', arg);
	}
	/*开启位置变化监听*/
	onLocationChange(fn, option) {
		const watchId = this.$xwbridge.$plus.geolocation.watchPosition((position) => {
			fn({
				...position.coords,
				...position.Address
			});
		}, (err) => {
			console.error('监听位置变化异常', err);
		}, Object.assign({
			maximumAge: 5000,
			timeout: 5000,
			geocode: false
		}, option));
		fn.locationWatchId = watchId;
	}
	/*关闭位置变化监听*/
	offLocationChange(fn) {
		this.$xwbridge.$plus.geolocation.clearWatch(fn.locationWatchId);
	}
	/*系统主题切换监听（深色和浅色模式）*/
	onThemeChange(fn) {
		if (fn == null || typeof fn !== 'function') {
			return;
		}
		const onUiStyleChange = () => {
			const style = this.$xwbridge.$plus.navigator.getUiStyle();
			fn({
				theme: style
			});
		}
		onUiStyleChange.__cid__ = fn.__cid__ = "ThemeChange-" + this.$xwbridge.genNonDuplicateId();
		callbackPool.set(onUiStyleChange.__cid__, onUiStyleChange);
		// 监听uistylechange消息
		document.addEventListener("uistylechange", onUiStyleChange, false);
	}
	/* 取消监听系统主题状态变化。 */
	offThemeChange(fn) {
		if (fn == null || typeof fn !== 'function') {
			return;
		}
		const cid = fn.__cid__;
		if (cid) {
			const callback = callbackPool.get(cid);
			document.removeEventListener('uistylechange', callback);
		}
	}

	/*监听网络状态变化。可使用uni.offNetworkStatusChange取消监听*/
	onNetworkStatusChange(fn) {
		if (fn == null || typeof fn !== 'function') {
			return;
		}
		const onNetChange = () => {
			let networkType;
			let isConnected = true;
			const nt = plus.networkinfo.getCurrentType();
			switch (nt) {
				case plus.networkinfo.CONNECTION_ETHERNET:
					networkType = 'ethernet';
					break;
				case plus.networkinfo.CONNECTION_WIFI:
					networkType = 'wifi';
					break;
				case plus.networkinfo.CONNECTION_CELL2G:
					networkType = '2g';
					break;
				case plus.networkinfo.CONNECTION_CELL3G:
					networkType = '3g';
					break;
				case plus.networkinfo.CONNECTION_CELL4G:
					networkType = '4g';
					break;
				case plus.networkinfo.CONNECTION_CELL5G:
					networkType = '5g';
					break;
				default:
					networkType = 'none';
					isConnected = false;
					break;
			}
			fn({
				isConnected,
				networkType
			});
		}
		onNetChange.__cid__ = fn.__cid__ = "NetChange-" + this.$xwbridge.genNonDuplicateId();
		callbackPool.set(onNetChange.__cid__, onNetChange);
		document.addEventListener('netchange', onNetChange, false);
	}

	/*取消监听网络状态变化。*/
	offNetworkStatusChange(fn) {
		if (fn == null || typeof fn !== 'function') {
			return;
		}
		const cid = fn.__cid__;
		if (cid) {
			const callback = callbackPool.get(cid);
			document.removeEventListener('netchange', callback);
		}
	}

	/*监听键盘高度变化*/
	onKeyboardHeightChange(fn){
		if (fn == null || typeof fn !== 'function') {
			return;
		}
		fn.__cid__ = "KeyboardHeightChange-" + this.$xwbridge.genNonDuplicateId();
		document.addEventListener("keyboardchange", fn, false);
	}

	/*取消监听键盘高度变化*/
	offKeyboardHeightChange(fn){
		if (fn == null || typeof fn !== 'function') {
			return;
		}
		if (fn.__cid__) {
			document.removeEventListener('keyboardchange', fn);
		}
	}
	
	$emit(name,...params){
		this._defaultInvokeCall('$emit')({name,params});
	}
	
	$on(name,fn){
		this._defaultInvokeCall('$on')({name,success:fn});
	}
	
	$once(name,fn){
		const warpFn = (data)=>{
			fn(data);
			callbackPool.delete(fn.__cid__);
		}
		const arg = {name,success:warpFn};
		this._defaultInvokeCall('$on')(arg);
		fn.__cid__ = warpFn.__cid__;
	}
	
	$off(name,fn){
		if(fn.__cid__){
			callbackPool.delete(fn.__cid__);
			this._defaultInvokeCall('$off')({name,success:fn});
		}
	}

	/*获取全局唯一的录音管理器 recorderManager*/
	getRecorderManager() {
		if (this.__recorderManager) {
			return this.__recorderManager;
		}
		const recorderManager = this.$xwbridge.$plus.audio.getRecorder();
		this.__recorderManager = {
			start(options = {}) {
				const duration = options.duration || 60000; // 录音的时长，单位 ms
				const sampleRate = options.sampleRate; //采样率，有效值 8000/16000/44100
				const format = options.format; //音频格式，有效值 aac/mp3/wav/PCM
				recorderManager.record({
						samplerate: sampleRate,
						format: format
					},
					(filepath) => {
						if (this.onStopCallback) {
							this.onStopCallback({
								tempFilePath: filepath
							});
						}
					},
					(err) => {
						if (this.onErrorCallback) {
							this.onErrorCallback(err);
						}
					}
				);
				if (this.onStartCallback) {
					this.onStartCallback();
				}
				setTimeout(() => {
					recorderManager.stop();
				}, duration);
			},
			stop() {
				recorderManager.stop();
			},
			onStart(fn) {
				this.onStartCallback = fn;
			},
			onStop(fn) {
				this.onStopCallback = fn;
			},
			onError(fn) {
				this.onErrorCallback = fn;
			}
		}
		return this.__recorderManager;
	}


	/**
	 * 当被xwbridge安装时调用
	 * @param {Object} xwbridge webview通信对象
	 */
	$onBridgeInstall(xwbridge) {
		this.$xwbridge = xwbridge;
		this.$xwbridge.$uni = this;
		this.$bridgeMessageTypes = ['uniapi-ack', 'uniapi'];
		this.$onBridgeMessage = (data) => {
			const callbackName = data.fn;
			const callbackData = data.res;
			const callback = callbackPool.get(callbackName);
			if (callback) {
				try {
					callback(callbackData);
				} catch (e) {
					throw e;
				}
			} else {
				throw new Error('callback: ' + callbackName + ' is not defind');
			}
		}
	}

}


export default new UniBridge();
